﻿#region [GameObject3D Using Statements]
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Content;
using WP7_XNA_Accelerometer_Simulator.Utils.GameInfo;
using WP7_XNA_Accelerometer_Simulator.Utils.Rotation;
#endregion

namespace WP7_XNA_Accelerometer_Simulator.GameObjects.SuperClasses
{
    /// <summary>
    /// It provides all methods and properties needed to create, update and draw 3D objects.
    /// </summary>
    /// <remarks>
    /// Every 3D drawable object should inherit from this class.
    /// </remarks>
    public abstract class GameObject3D
    {
        #region [GameObject3D Fields]
        private Vector3 _rotation;
        #endregion

        #region GameObject3D Properties
        protected Matrix[] BonesCountMatrix { get; set; }
        public virtual Vector3 Position { get; set; }
        public Model GameModel { get; set; }
        public Vector3 Scale { get; set; }
        public Quaternion QuatRotationMatrix { get; set; }

        public Vector3 Rotation
        {
            get
            {
                return _rotation;
            }
            set
            {
                _rotation = value;

                /*just keeping the values between 0 and 360 degrees*/
                if (_rotation.X > 2*MathHelper.Pi)
                    _rotation.X = _rotation.X - 2 * MathHelper.Pi;
                if (_rotation.Y > 2*MathHelper.Pi)
                    _rotation.Y = _rotation.Y - 2 * MathHelper.Pi;
                if (_rotation.Z > 2*MathHelper.Pi)
                    _rotation.Z = _rotation.Z - 2 * MathHelper.Pi;

                if (_rotation.X < -2 * MathHelper.Pi)
                    _rotation.X = _rotation.X - (-2 * MathHelper.Pi);
                if (_rotation.Y < -2 * MathHelper.Pi)
                    _rotation.Y = _rotation.Y - (-2 * MathHelper.Pi);
                if (_rotation.Z < -2 * MathHelper.Pi)
                    _rotation.Z = _rotation.Z - (-2 * MathHelper.Pi);

            }
        }
        #endregion

        #region [GameObject3D Constructor]
        public GameObject3D()
        {
            Position = Vector3.Zero;
            Scale = new Vector3(1, 1, 1);
            Rotation = Vector3.Zero;
            GameModel = null;
            QuatRotationMatrix = Quaternion.Identity;
        }
        #endregion

        #region [GameObject3D LoadContent and GameLoop (Update and Draw)]
        /// <summary>
        /// LoadContent will be called once per game and is the place to load
        /// all of your content.
        /// </summary>
        /// <param name="content">ContentManager used by the game</param>
        /// Since the models don't change over time we only need to 
        /// do the CopyAbsulteBoneTransformsTo method once, so
        /// we do it here.
        public virtual void LoadContent(ContentManager content)
        {
            BonesCountMatrix = new Matrix[GameModel.Bones.Count];
            GameModel.CopyAbsoluteBoneTransformsTo(BonesCountMatrix);
        }


        /// <summary>
        /// Allows the game to run logic such as updating the world,
        /// checking for collisions, gathering input, and playing audio.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        public virtual void Update(GameTime gameTime)
        {
            ;
        }
            
        /// <summary>
        /// Allows the game to draw the this object
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values</param>
        public virtual void Draw(GameTime gameTime)
        {
            Matrix modelRotation = Matrix.CreateFromQuaternion(QuatRotationMatrix);
            Matrix modelPosition = Matrix.CreateTranslation(Position);
            Matrix modelScale = Matrix.CreateScale(Scale);

            foreach (ModelMesh mesh in GameModel.Meshes)
            {
                foreach (BasicEffect effect in mesh.Effects)
                {
                    effect.EnableDefaultLighting();
                    effect.PreferPerPixelLighting = true;
                    effect.World = BonesCountMatrix[mesh.ParentBone.Index] * modelScale * modelRotation * modelPosition;
                    effect.Projection = GameManager.CameraProjectionMatrix;
                    effect.View = GameManager.CameraViewMatrix;
                }
                mesh.Draw();
            }
        }
        #endregion

        #region GameObject3D Private/Auxiliary Methods]
        /// <summary>
        /// Rotates de Model around an axis
        /// </summary>
        /// <param name="axis">The axis on which to perform the rotation</param>
        /// <param name="fDegrees">The quantity of degrees to rotate</param>
        private void rotate(Vector3 axis, float fDegrees)
        {
            axis = Vector3.Transform(axis, Matrix.CreateFromQuaternion(QuatRotationMatrix));

            QuatRotationMatrix = Quaternion.Normalize(Quaternion.CreateFromAxisAngle(axis, MathHelper.ToRadians(fDegrees)) *
                QuatRotationMatrix);
        }
        #endregion

        #region GameObject3D Public Methods]
        /// <summary>
        /// Rotates the Model around the Z axis
        /// </summary>
        /// <param name="fDegrees">The quantity of degrees to rotate</param>
        public void Pitch(float fDegrees)
        {
            rotate(new Vector3(0, 0, 1), fDegrees);
        }

        /// <summary>
        /// Rotates the Model around the Y axis
        /// </summary>
        /// <param name="fDegrees">The quantity of degrees to rotate</param>
        public void Yaw(float fDegrees)
        {
            rotate(new Vector3(0, 1, 0), fDegrees);
        }

        /// <summary>
        /// Rotates the Model around the X axis
        /// </summary>
        /// <param name="fDegrees">The quantity of degrees to rotate</param>
        public void Roll(float fDegrees)
        {
            rotate(new Vector3(1, 0, 0), fDegrees);
        }

        /// <summary>
        /// It will do whatever needs to be done when
        /// a rotation is applied to the GameObject
        /// </summary>
        /// <param name="rotType">The type of the rotation (Pitch/Yaw/Roll)</param>
        /// <param name="rotQuantity">How many degress to rotate</param>
        ///<remarks>
        ///The world axis won't do anything, the rotation controls
        ///will just rotate according to the rotation applied and
        ///the Windows Phone 7 will rotate and upload acceleration
        ///values to the server
        /// </remarks>
        public virtual void ProcessRotation(RotationUtil.RotationType rotType, float rotDegrees)
        {
            Vector3 rot = Rotation;

            switch (rotType)
            {
                case (RotationUtil.RotationType.Pitch):
                    rot.Z += MathHelper.ToRadians(rotDegrees);
                    Pitch(rotDegrees);
                    break;
                case (RotationUtil.RotationType.Roll):
                    rot.X += MathHelper.ToRadians(rotDegrees);
                    Roll(rotDegrees);
                    break;
                case (RotationUtil.RotationType.Yaw):
                    rot.Y += MathHelper.ToRadians(rotDegrees);
                    Yaw(rotDegrees);
                    break;
                default:
                    break;
            }

            Rotation = rot;
        }
        #endregion
    }
}
